package com.dkts.oil.util.gis;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import com.alibaba.fastjson2.JSONObject;

public class WGeoHash {
    /* (Geohash-specific) Base32 map */
    private static String BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz";
    final static Map<String, String[]> BEIGHBOUR = new HashMap<>();
    final static Map<String, String[]> BORDER = new HashMap<>();
    static {
        BEIGHBOUR.put("n", new String[]{"p0r21436x8zb9dcf5h7kjnmqesgutwvy", "bc01fg45238967deuvhjyznpkmstqrwx"});
        BEIGHBOUR.put("s", new String[]{"14365h7k9dcfesgujnmqp0r2twvyx8zb", "238967debc01fg45kmstqrwxuvhjyznp"});
        BEIGHBOUR.put("e", new String[]{"bc01fg45238967deuvhjyznpkmstqrwx", "p0r21436x8zb9dcf5h7kjnmqesgutwvy"});
        BEIGHBOUR.put("w", new String[]{"238967debc01fg45kmstqrwxuvhjyznp", "14365h7k9dcfesgujnmqp0r2twvyx8zb"});

        BORDER.put("n", new String[]{"prxz",     "bcfguvyz"});
        BORDER.put("s", new String[]{"028b",     "0145hjnp"});
        BORDER.put("e", new String[]{"bcfguvyz", "prxz"});
        BORDER.put("w", new String[]{"0145hjnp", "028b"});
    }
    public  static String getGeoHash(LinkedHashMap centerPoint,int precision) {
        try {
            double lat = Double.valueOf(centerPoint.get("lat") + "");
            double lng = Double.valueOf(centerPoint.get("lon") + "");
            return encode(lat, lng, precision);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Encodes latitude/longitude to geohash, either to specified precision or to automatically
     * evaluated precision.
     *
     * @param   {number} lat - Latitude in degrees.
     * @param   {number} lon - Longitude in degrees.
     * @param   {number} [precision] - Number of characters in resulting geohash.
     * @returns {string} Geohash of supplied latitude/longitude.
     *
     * @example
     *     var geohash = Geohash.encode(52.205, 0.119, 7); // geohash: 'u120fxw'
     */
    public static String encode(Double lat, Double lon) throws Exception {
        return encode(lat, lon, -1);
    }

    public static String encodeGps(String gps, int precision) throws Exception {
        String [] gpsArr = gps.split(",");
        return encode(Double.valueOf(gpsArr[0]), Double.valueOf(gpsArr[1]), precision);
    }
    public static String encode(Double lat, Double lon, int precision) throws Exception {
        int _precision = precision;
        // infer precision?
        if (_precision == -1) {
            // refine geohash until it matches precision of supplied lat/lon
            for (int p = 1; p <= 12; p++) {
                String hash = encode(lat, lon, p);
                Double[] posn = decode(hash);
                if (posn[0] == lat && posn[1] == lon) 
                  return hash;
            }
            _precision = 12; // set to maximum
        }

        int idx = 0; // index into base32 map
        int bit = 0;// each char holds 5 bits
        boolean evenBit = true;
        String geohash = "";

        double latMin = -90.0, latMax = 90.0, lonMin = -180.0, lonMax = 180.0;

        while (geohash.length() < _precision) {
            if (evenBit) {
                // bisect E-W longitude
                Double lonMid = (lonMin + lonMax) / 2;
                if (lon >= lonMid) {
                    idx = idx * 2 + 1;
                    lonMin = lonMid;
                } else {
                    idx = idx * 2;
                    lonMax = lonMid;
                }
            } else {
                // bisect N-S latitude
                Double latMid = (latMin + latMax) / 2;
                if (lat >= latMid) {
                    idx = idx * 2 + 1;
                    latMin = latMid;
                } else {
                    idx = idx * 2;
                    latMax = latMid;
                }
            }
            evenBit = !evenBit;

            if ((bit + 1) == 5) {
                // 5 bits gives us a character: append it and start over
                geohash = geohash + BASE32.charAt(idx);
                bit = 0;
                idx = 0;
            } else bit = bit + 1;
        }
        return geohash;
    }


    /**
     * Decode geohash to latitude/longitude (location is approximate centre of geohash cell,
     *     to reasonable precision).
     *
     * @param   {string} geohash - Geohash string to be converted to latitude/longitude.
     * @returns {{lat:number, lon:number}} (Center of) geohashed location.
     * @throws  Invalid geohash.
     *
     * @example
     *     var latlon = Geohash.decode('u120fxw'); // latlon: { lat: 52.205, lon: 0.1188 }
     */
    public static Double[] decode(String geohash) throws Exception {
        Double[] _bounds = bounds(geohash); // <-- the hard work
        // now just determine the centre of the cell...

        Double latMin = _bounds[0], lonMin = _bounds[1];
        Double latMax = _bounds[2], lonMax = _bounds[3];

        // cell centre
        Double lat = (latMin + latMax) / 2;
        Double lon = (lonMin + lonMax) / 2;

        // round to close to centre without excessive precision: ⌊2-log10(Δ°)⌋ decimal places
        double _t = Math.floor(2 - Math.log(latMax-latMin) / Math.log(10));
        lat = lat * Math.pow(10, _t) / Math.pow(10, _t);
        _t = Math.floor(2 - Math.log(lonMax-lonMin) / Math.log(10));
        lon = lon * Math.pow(10, _t) / Math.pow(10, _t);

        return new Double[]{ lat, lon };
    }
    
    /**
     * Returns SW/NE latitude/longitude bounds of specified geohash.
     *
     * @param   {string} geohash - Cell that bounds are required of.
     * @returns {{sw: {lat: number, lon: number}, ne: {lat: number, lon: number}}}
     * @throws  Invalid geohash.
     */
    public static Double[] bounds(String geohash) throws Exception {
        if (geohash.length() == 0) throw new Exception("Invalid geohash");

        String geohash1 = geohash.toLowerCase();

        boolean evenBit = true;
        Double latMin = -90.0, latMax = 90.0, lonMin = -180.0, lonMax = 180.0;

        for (int i = 0; i < geohash1.length(); i++) {
            char chr = geohash1.charAt(i);
            int idx = BASE32.indexOf(chr);
            if (idx == -1) throw new Exception("Invalid geohash");

            for (int n = 4; n >= 0;  n--) {
                int bitN = idx >> n & 1;
                if (evenBit) {
                    // longitude
                    var lonMid = (lonMin+lonMax) / 2;
                    if (bitN == 1) {
                        lonMin = lonMid;
                    } else {
                        lonMax = lonMid;
                    }
                } else {
                    // latitude
                    var latMid = (latMin+latMax) / 2;
                    if (bitN == 1) {
                        latMin = latMid;
                    } else {
                        latMax = latMid;
                    }
                }
                evenBit = !evenBit;
            }
        }

        return new Double[]{latMin, lonMin, latMax, lonMax}; //sw, ne
    }

    /**
     * Determines adjacent cell in given direction.
     *
     * @param   geohash - Cell to which adjacent cell is required.
     * @param   direction - Direction from geohash (N/S/E/W).
     * @throws Exception 
     * @returns {string} Geocode of adjacent cell.
     * @throws  Invalid geohash.
    */
    public static String adjacent(String geohash, String direction) throws Exception {
        String _geohash = geohash.toLowerCase();
        String _direction = direction.toLowerCase();

        if (_geohash.length() == 0) throw new Exception("Geohash invalid :" + geohash);
        if ("nsew".indexOf(direction) == -1) throw new Exception("Invalid direction");

        char lastCh = _geohash.charAt(_geohash.length() - 1);    // last character of hash
        String parent = _geohash.substring(0, _geohash.length() - 1); // hash without last character

        int _type = _geohash.length() % 2;

        // check for edge-cases which don't share common prefix
        if (BORDER.get(_direction)[_type].indexOf(lastCh) != -1 && parent != "") {
            parent = adjacent(parent, _direction);
        }

        // append letter for direction to parent
        return parent + BASE32.charAt(BEIGHBOUR.get(_direction)[_type].indexOf(lastCh));
    }

    /**
     * Returns all 8 adjacent cells to specified geohash.
     *
     * @param   {string} geohash - Geohash neighbours are required of.
     * @returns {{n,ne,e,se,s,sw,w,nw: string}}
     * @throws  Invalid geohash.
     */
    public static Map<String, String> neighbours(String geohash) throws Exception {
        Map<String, String> result = new HashMap<>();
        result.put("n", adjacent(geohash, "n"));
        result.put("ne", adjacent(adjacent(geohash, "n"), "e"));
        result.put("e", adjacent(geohash, "e"));
        result.put("se", adjacent(adjacent(geohash, "s"),"e"));
        result.put("s", adjacent(geohash, "s"));
        result.put("sw", adjacent(adjacent(geohash, "s"),"w"));
        result.put("w", adjacent(geohash, "w"));
        result.put("nw", adjacent(adjacent(geohash, "n"), "w"));
        return result;
    }


    public static String parent(String[] geohashs) {
        String[] _geohashs = geohashs;
        String parent = "";

        for (int i = 0; i < _geohashs[0].length(); i++) {
            char _ch = _geohashs[0].charAt(i);
            Boolean isMath = true;
            
            for (int i2 = 1; i2 < _geohashs.length; i2++) {
                if (_geohashs[i2].length() <= i || _geohashs[i2].charAt(i) != _ch) {
                    isMath = false;
                    break;
                }
            }
            
            if (isMath) parent += _ch;
            else break;
        }
        return parent.toLowerCase();
    }


    public static void main(String []args) throws Exception {
    }
}